﻿using System;
using System.Diagnostics;
using System.Linq;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using ChatRoom.Protocol;
using TingSLsocket;

namespace ChatRoom.Web.Server
{
    public class ChatServer : IDisposable, IServer
    {
        // **************************************** //
        #region "member fields"

        private readonly TcpServer _tcpserver;
        private readonly ConcurrentDictionary<Guid, ChatProxy> _proxies;

        #endregion

        // **************************************** //
        #region "constructor and destructor"

        public ChatServer(int numClients0, int bufSize)
        {
            _tcpserver = new TcpServer(numClients0, bufSize);
            _tcpserver.DataRecvExposeEvt += OnDataReceived;
            _tcpserver.ClientConnectedEvt += OnClientConnected;
            _tcpserver.ClientDisconnectedEvt += OnClientDisconnected;
            _tcpserver.ClientErrorEvt += OnClientError;

            _proxies = new ConcurrentDictionary<Guid, ChatProxy>();
        }

        public void Dispose()
        {
            _tcpserver.Dispose();
            foreach (var kv in _proxies)
            {
                kv.Value.Dispose();
            }
        }

        #endregion

        // **************************************** //
        #region "public API"

        public void Start()
        {
            _tcpserver.Start(Constants.ServerPort);
        }

        public void Forward(Guid srcId, byte[] buffer)
        {
            var peers = (from kv in _proxies
                         where kv.Key != srcId
                         select kv.Key).ToArray();// copy to avoid conflicts
            foreach (var peerId in peers)
            {
                _tcpserver.SendAsync(peerId, buffer, 0, buffer.Length);
            }
        }

        #endregion

        // **************************************** //
        #region "private helpers"

        private void OnDataReceived(object sender, Tuple<Guid, byte[], int, int> args)
        {
            ChatProxy proxy = null;
            if (_proxies.TryGetValue(args.Item1, out proxy))
            {
                proxy.Receive(args.Item2, args.Item3, args.Item4);
            }
        }

        private void OnClientConnected(object sender, Guid clientId)
        {
            Tuple<bool, TcpRemoteChannel> result = _tcpserver.TryGetRemoteClient(clientId);
            if (!result.Item1)
            {
                return;
            }

            var clientEndpnt = (IPEndPoint) result.Item2.RemoteEndPnt;
            var newClientInfo = new ClientInfo
                                    {
                                        Id = clientId,
                                        Host = clientEndpnt.Address.ToString(),
                                        Port = clientEndpnt.Port
                                    };
            var proxy = new ChatProxy(newClientInfo, this);

            if (_proxies.TryAdd(clientId, proxy))
            {
                SendClientList();
                Trace.TraceInformation("[+] client<{0}:{1}> connected", newClientInfo.Host, newClientInfo.Port);
            }
            else
            {
                proxy.Dispose();
            }
        }

        private void SendClientList()
        {
            var clientList = (from kv in _proxies
                              select kv.Value.ClientInfo).ToArray();
            var serializer = new CommandSerializer();
            byte[] buffer = serializer.Serialize(Constants.CmdRefreshClients, clientList);

            // Guid.Empty means publishing
            _tcpserver.SendAsync(Guid.Empty, buffer, 0, buffer.Length);
        }

        private void OnClientDisconnected(object sender, Guid clientId)
        {
            ChatProxy proxy = null;
            if (_proxies.TryRemove(clientId, out proxy))
            {
                proxy.Dispose();
                SendClientList();
                Trace.TraceInformation("[-] client<{0}:{1}> connected", proxy.ClientInfo.Host, proxy.ClientInfo.Port);
            }
        }

        private void OnClientError(object sender, Tuple<Guid, SocketError> args)
        {
            Trace.TraceError("!!! ERROR !!!: {0}", args.Item2);
        }

        #endregion
    }
}